package __goroutines

/**
 Context
 是什么
	是一个接口，定义了WithCancel、WithDeadline、WithTimeout等方法
	主要用于父子任务之间的同步取消信号，本质上是一种协程调度的方式
 为什么
	使用通道的方式不够标准化，写起来麻烦
	如果父context取消，那么所有子协程都会取消
 怎么用
	查看官方示例
 练习
	1. 每隔5秒执行一次
	2. 10秒后自动结束
 原理
	看看源码如何实现的
	1. context.Background()
	实现了context接口，作为根节点，不能取消，没有超时时间，没有value，t odo 其实也一样
	2. context.WithCancel()
	返回 cancelCtx, 调用 cancel() 会取消所有子节点
	3. context.WithDeadline()
	返回 timerCtx，就是 cancelCtx + 定时器 + 时间间隔
	4. context.WithTimeout()
	其实就是用WithDeadline,传参不同而已
*/

import (
	"context"
	"fmt"
	"sync"
	"time"
)

// AutoEnd 自动结束
func AutoEnd() {
	var wg sync.WaitGroup
	wg.Add(1)
	ctx, cancel := context.WithTimeout(context.Background(), 2 * time.Second)
	defer cancel()

	go func(ctx context.Context) {
		select {
			case <- ctx.Done():
				fmt.Println("auto end")
				wg.Done()
		}
	}(ctx)
	wg.Wait()
}

// RywTicker 每5秒执行一次，执行两次
func RywTicker() {
	var wg sync.WaitGroup
	wg.Add(1)
	ctx, cancel := context.WithCancel(context.Background())
	//defer cancel()

	go func(ctx context.Context) {
		var times = 2
		var start = 0
		for {
			select {
			case <-ctx.Done():
				fmt.Println("ctx cancel")
				wg.Done()
				return
			case <-time.After(1 * time.Second):
				if start == times {
					wg.Done()
					return
				}
				start++
				fmt.Println("something happened")
			}
		}
	}(ctx)

	time.Sleep(1 * time.Second)
	cancel()

	wg.Wait()
}

func WithCancel() {
	// gen generates integers in a separate goroutine and
	// sends them to the returned channel.
	// The callers of gen need to cancel the context once
	// they are done consuming generated integers not to leak
	// the internal goroutine started by gen.
	gen := func(ctx context.Context) <-chan int {
		dst := make(chan int)
		n := 1
		go func() {
			for {
				select {
				case <-ctx.Done():
					return // returning not to leak the goroutine
				case dst <- n:
					time.Sleep(time.Second)
					n++
				}
			}
		}()
		return dst
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel() // cancel when we are finished consuming integers

	for n := range gen(ctx) {
		fmt.Println(n)
		if n == 5 {
			break
		}
	}
}

const shortDuration = 2 * time.Millisecond

func WithDeadline() {
	d := time.Now().Add(shortDuration)
	ctx, cancel := context.WithDeadline(context.Background(), d)

	// Even though ctx will be expired, it is good practice to call its
	// cancellation function in any case. Failure to do so may keep the
	// context and its parent alive longer than necessary.
	defer cancel()

	select {
	case <-time.After(1 * time.Second):
		fmt.Println("overslept")
	case <-ctx.Done():
		fmt.Println(ctx.Err())
	}
}

func WithTimeout() {
	ctx, cancel := context.WithTimeout(context.Background(), shortDuration)

	// Even though ctx will be expired, it is good practice to call its
	// cancellation function in any case. Failure to do so may keep the
	// context and its parent alive longer than necessary.
	defer cancel()

	select {
	case <-time.After(1 * time.Second):
		fmt.Println("overslept")
	case <-ctx.Done():
		fmt.Println(ctx.Err())
	}
}

func WithValue() {
	// 自己定义类型，避免在包之间发生冲突
	type favContextKey string

	f := func(ctx context.Context, k favContextKey) {
		if v := ctx.Value(k); v != nil {
			fmt.Println("found value:", v)
			return
		}
		fmt.Println("key not found:", k)
	}

	k := favContextKey("language")
	ctx := context.WithValue(context.Background(), k, "Go")

	f(ctx, k)
	f(ctx, "color")
}
